#include "TextureCube.hpp"
#include <Math/Math.hpp>
#include <Utility/StringTools.hpp>
#include <Utility/FileTools.hpp>

namespace zzz{

TextureCube::TextureCube(int newiformat)
:Texture(GL_TEXTURE_CUBE_MAP, newiformat)
{}

TextureCube::TextureCube(zuint32 size, int newiformat)
:Texture(GL_TEXTURE_CUBE_MAP, newiformat)
{
  TextureCube(size,size,newiformat);
}

TextureCube::TextureCube(zuint32 width, zuint32 height, int newiformat)
:Texture(GL_TEXTURE_CUBE_MAP, newiformat)
{
  Create(width,height);
}

TextureCube::TextureCube(const string &filename, int newiformat)
:Texture(GL_TEXTURE_CUBE_MAP, newiformat)
{
  Create(filename);
}

TextureCube::TextureCube(const string &PosX, const string &NegX, const string &PoxY, const string &NegY, const string &PosZ, const string &NegZ, int newiformat)
:Texture(GL_TEXTURE_CUBE_MAP, newiformat)
{
  Create(PosX,NegX,PoxY,NegY,PosZ,NegZ);
}


bool TextureCube::Bind(GLenum bindto/*=GL_TEXTURE0*/)
{
  int lastLevel;
  glGetIntegerv(GL_ACTIVE_TEXTURE,&lastLevel);

  glActiveTexture(bindto);
  Disable(bindto);
  glEnable(GL_TEXTURE_CUBE_MAP);
  GLBindTextureCube::Set(GetID());

  glActiveTexture(lastLevel);
  CHECK_GL_ERROR()
  return true;
}

void TextureCube::Create(zuint32 size)
{
  Create(size,size);
}

void TextureCube::Create(zuint width, zuint height)
{
  width_=width;
  height_=height;
  
  glBindTexture(GL_TEXTURE_CUBE_MAP,GetID());
  glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, filter_para_);
  glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, filter_para_);
  glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, wrap_para_);
  glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, wrap_para_);

  glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X,0,internal_format_,width,height,0,0,0,NULL);
  glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X,0,internal_format_,width,height,0,0,0,NULL);
  glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y,0,internal_format_,width,height,0,0,0,NULL);
  glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,0,internal_format_,width,height,0,0,0,NULL);
  glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z,0,internal_format_,width,height,0,0,0,NULL);
  glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z,0,internal_format_,width,height,0,0,0,NULL);

  CHECK_GL_ERROR()
}

void TextureCube::Create(const string &filename)
{
  string ext=filename;
  ext=GetExt(ext);
  ToLow(ext);
  if (ext==".pfm")
    if (!ReadPfm(filename))
    {
      printf("ERROR: ReadPfm: %s\n",filename);
    }
  CHECK_GL_ERROR()
}

void TextureCube::Create(const string &PosX, const string &NegX, const string &PosY, const string &NegY, const string &PosZ, const string &NegZ)
{
  Image4f img[6];
  img[POSX].LoadFile(PosX);
  img[NEGX].LoadFile(NegX);
  img[POSY].LoadFile(PosY);
  img[NEGY].LoadFile(NegY);
  img[POSZ].LoadFile(PosZ);
  img[NEGZ].LoadFile(NegZ);
  ImageToTexture(img[POSX],img[NEGX],img[POSY],img[NEGY],img[POSZ],img[NEGZ]);
  CHECK_GL_ERROR()
}

void TextureCube::ChangeInternalFormat(int newiformat)
{
  if (internal_format_==newiformat) return;
  Image4f img[6];
  TextureToImage(img[POSX],img[NEGX],img[POSY],img[NEGY],img[POSZ],img[NEGZ]);
  internal_format_=newiformat;
  ImageToTexture(img[POSX],img[NEGX],img[POSY],img[NEGY],img[POSZ],img[NEGZ]);
  CHECK_GL_ERROR()
}

void TextureCube::ChangeSize(zuint32 width,zuint32 height)
{
  if (width_==width && height_==height) return;
  Image4f img[6];
  TextureToImage(img[POSX],img[NEGX],img[POSY],img[NEGY],img[POSZ],img[NEGZ]);
  for (zuint i=0; i<6; i++)
    img[i].Resize(height,width);
  ImageToTexture(img[POSX],img[NEGX],img[POSY],img[NEGY],img[POSZ],img[NEGZ]);
  CHECK_GL_ERROR()
}

void TextureCube::ChangeSize(zuint32 size)
{
  ChangeSize(size,size);
}

bool TextureCube::ReadPfm(const string &filename)
{
  FILE *fp;
  fp=fopen(filename.c_str(),"r");
  if (fp==NULL)
    return false;

  char s[1000];
  s[0]=0;
  int width,height;
  int headerlen=0;
  if (fscanf(fp,"%[^\x0a]\x0a",s)!=1) {
    fclose(fp);
    return false;
  }
  if ((strlen(s)<2)||(s[0]!='P')||(s[1]!='f'&&s[1]!='F')) {
    fclose(fp);
    return false;
  }
  headerlen+=(int)strlen(s)+1;

  if (fscanf(fp,"%[^\x0a]\x0a",s)!=1) {
    fclose(fp);
    return false;
  }
  if (sscanf(s,"%d %d",&width,&height)!=2) {
    fclose(fp);
    return false;
  }
  if (width%3!=0||height%4!=0||(((width/3)&(width/3-1))!=0)||(((height/4)&(height/4-1))!=0)||width/3!=height/4) {
    fclose(fp);
    return false;
  }
  headerlen+=(int)strlen(s)+1;

  if (fscanf(fp,"%[^\x0a]\x0a",s)!=1) {
    fclose(fp);
    return false;
  }
  headerlen+=(int)strlen(s)+1;

  fclose(fp);

  fp=fopen(filename.c_str(),"rb");
  float *data;
  data=new float[width*height*3];

  if (fseek(fp,headerlen,SEEK_SET)!=0) return false;
  int ii=(int)fread((void *)data,1,width*height*3*(int)sizeof(float),fp);
  if (ii!=width*height*3*(int)sizeof(float))
  {
    delete[] data;
    data=0;
    fclose(fp);
    return false;
  }
  fclose(fp);

  width_=width/3;
  height_=height/4;

  Image3f image[6];
  for (zuint i=0; i<6; i++)
    image[i].SetSize(height_,width_);
  
  float *imagedata;
  imagedata=(float *)image[POSX].Data();
  for (zuint i=0; i<height_; i++) for (zuint j=0; j<width_; j++)
    memcpy(imagedata+(width_*i+j)*3,data+((height_*3-1-i)*width+width_*3-1-j)*3,sizeof(float)*3);

  imagedata=(float *)image[NEGX].Data();
  for (zuint i=0; i<height_; i++) for (zuint j=0; j<width_; j++)
    memcpy(imagedata+(width_*i+j)*3,data+((height_*3-1-i)*width+width_-1-j)*3,sizeof(float)*3);

  imagedata=(float *)image[POSY].Data();
  for (zuint i=0; i<height_; i++) 
    memcpy(imagedata+width_*i*3,data+((height_*3+i)*width+width_)*3,sizeof(float)*3*width_);

  imagedata=(float *)image[NEGY].Data();
  for (zuint i=0; i<height_; i++)
    memcpy(imagedata+width_*3*i,data+((height_*1+i)*width+width_)*3,sizeof(float)*3*width_);

  imagedata=(float *)image[POSZ].Data();
  for (zuint i=0; i<height_; i++)
    memcpy(imagedata+width_*3*i,data+((height_*0+i)*width+width_)*3,sizeof(float)*3*width_);

  imagedata=(float *)image[NEGZ].Data();
  for (zuint i=0; i<height_; i++) for (zuint j=0; j<width_; j++)
    memcpy(imagedata+(width_*i+j)*3,data+((height_*3-1-i)*width+width_*2-1-j)*3,sizeof(float)*3);

  delete[] data;

  CHECK_GL_ERROR()
  return true;
}

bool TextureCube::SavePfm(const string &filename)
{
  char s1[1000];
  FILE *fp=fopen(filename.c_str(),"wb");
  if (fp==NULL) return false;
  sprintf(s1,"PF\x0a%d %d\x0a-1.000000\x0a",width_*3,width_*4);
  fwrite(s1,1,strlen(s1),fp);

  int width=width_*3;
  int height=height_*4;
  float *data;
  data=new float[width*height*3];
  memset(data,0,sizeof(float)*width*height*3);

  Image3f image[6];
  TextureToImage(image[POSX],image[NEGX],image[POSY],image[NEGY],image[POSZ],image[NEGZ]);
  float *imagedata;

  imagedata=(float *)image[POSX].Data();
  for (zuint i=0; i<height_; i++) for (zuint j=0; j<width_; j++)
    memcpy(data+((height_*3-1-i)*width+width_*3-1-j)*3,imagedata+(width_*i+j)*3,sizeof(float)*3);

  imagedata=(float *)image[NEGX].Data();
  for (zuint i=0; i<height_; i++) for (zuint j=0; j<width_; j++)
    memcpy(data+((height_*3-1-i)*width+width_-1-j)*3,imagedata+(width_*i+j)*3,sizeof(float)*3);

  imagedata=(float *)image[POSY].Data();
  for (zuint i=0; i<height_; i++) 
    memcpy(data+((height_*3+i)*width+width_)*3,imagedata+width_*i*3,sizeof(float)*3*width_);

  imagedata=(float *)image[NEGY].Data();
  for (zuint i=0; i<height_; i++)
    memcpy(data+((height_*1+i)*width+width_)*3,imagedata+width_*3*i,sizeof(float)*3*width_);

  imagedata=(float *)image[POSZ].Data();
  for (zuint i=0; i<height_; i++)
    memcpy(data+((height_*0+i)*width+width_)*3,imagedata+width_*3*i,sizeof(float)*3*width_);

  imagedata=(float *)image[NEGZ].Data();
  for (zuint i=0; i<height_; i++) for (zuint j=0; j<width_; j++)
    memcpy(data+((height_*3-1-i)*width+width_*2-1-j)*3,imagedata+(width_*i+j)*3,sizeof(float)*3);

  fwrite(data,1,width*height*3*sizeof(float),fp);
  fclose(fp);

  delete[] data;
  CHECK_GL_ERROR()
  return true;
}

bool TextureCube::DrawCubemap(float size /*=1*/)
{
  if (!IsValid())
    return false;

  Bind(GL_TEXTURE0);

  int lastEnv;
  glGetTexEnviv(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,&lastEnv);
  glTexEnvi(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_DECAL);
  glDepthMask(0);

  int s=width_;
  double t=(s-1)/(double)s;
  glColor3f(0.5,0.5,0.5);
  //front face
  glBegin(GL_QUADS);
  glTexCoord3d(-t,-t,1);  glVertex3d(-size,-size,size);
  glTexCoord3d(t,-t,1);  glVertex3d(size,-size,size);
  glTexCoord3d(t,t,1);  glVertex3d(size,size,size);
  glTexCoord3d(-t,t,1);  glVertex3d(-size,size,size);
  //left face
  glTexCoord3d(-1,-t,-t);  glVertex3d(-size,-size,-size);
  glTexCoord3d(-1,-t,t);  glVertex3d(-size,-size,size);
  glTexCoord3d(-1,t,t);  glVertex3d(-size,size,size);
  glTexCoord3d(-1,t,-t);  glVertex3d(-size,size,-size);
  //right face
  glTexCoord3d(1,-t,t);  glVertex3d(size,-size,size);
  glTexCoord3d(1,-t,-t);  glVertex3d(size,-size,-size);
  glTexCoord3d(1,t,-t);  glVertex3d(size,size,-size);
  glTexCoord3d(1,t,t);  glVertex3d(size,size,size);
  //back face
  glTexCoord3d(t,-t,-1);  glVertex3d(size,-size,-size);
  glTexCoord3d(-t,-t,-1);  glVertex3d(-size,-size,-size);
  glTexCoord3d(-t,t,-1);  glVertex3d(-size,size,-size);
  glTexCoord3d(t,t,-1);  glVertex3d(size,size,-size);
  //top face
  glTexCoord3d(-t,1,t);  glVertex3d(-size,size,size);
  glTexCoord3d(t,1,t);  glVertex3d(size,size,size);
  glTexCoord3d(t,1,-t);  glVertex3d(size,size,-size);
  glTexCoord3d(-t,1,-t);  glVertex3d(-size,size,-size);
  //bottom face
  glTexCoord3d(-t,-1,-t);  glVertex3d(-size,-size,-size);
  glTexCoord3d(t,-1,-t);  glVertex3d(size,-size,-size);
  glTexCoord3d(t,-1,t);  glVertex3d(size,-size,size);
  glTexCoord3d(-t,-1,t);  glVertex3d(-size,-size,size);
  glEnd();

  glTexEnvi(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,lastEnv);
  glDepthMask(1);

  CHECK_GL_ERROR()
  return true;

}

void TextureCube::ChangeParameter(GLenum filter,GLenum wrap)
{
  Bind();
  
  if (filter!=0)
  {
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, filter);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, filter);
  }
  if (wrap!=0)
  {
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, wrap);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, wrap);
  }
  filter_para_=filter;
  wrap_para_=filter;
  CHECK_GL_ERROR()
}
}
